"""
在线客服系统 - WebSocket 连接管理
用于管理用户和客服的 WebSocket 连接，实现实时聊天功能
"""

# 导入标准库
from typing import Dict, Set, Optional  # 类型提示
import json  # JSON处理
import asyncio  # 异步操作


# 全局变量：存储用户连接字典
# 数据结构设计：以用户ID为键，WebSocket连接对象为值的字典
# 作用：维护所有已连接用户的状态，便于消息转发和连接管理
userdict: Dict[str, object] = {}  # {user_id: WebSocket连接}

# 全局变量：存储客服连接字典
# 数据结构设计：以客服ID为键，WebSocket连接对象为值的字典
# 作用：维护所有已连接客服的状态，实现消息分发和负载均衡
servicedict: Dict[str, object] = {}  # {service_id: WebSocket连接}

# 客服ID（写死）
# 系统设计说明：当前系统采用单一客服模式，固定客服ID为'1001'
# 扩展性考虑：在多客服系统中，此设计需要扩展为动态分配机制
serviceid = '1001'


async def broadcast(message: dict):
    """
    广播消息给所有连接的客户端（用户和客服）
    
    广播机制说明：
    1. 将消息序列化为JSON格式字符串
    2. 遍历所有用户连接，逐一发送消息
    3. 遍历所有客服连接，逐一发送消息
    4. 自动清理已断开的连接，维护连接池的准确性
    
    参数:
        message: 要广播的消息（字典格式）
              通常包含消息类型、发送者、内容等字段
    """
    # 将消息字典序列化为JSON字符串，确保中文字符正确处理
    # ensure_ascii=False 参数保证中文字符不被转义为Unicode编码
    message_text = json.dumps(message, ensure_ascii=False)
    
    # 向所有用户广播消息
    # disconnected_users列表用于收集发送失败的用户ID，后续统一清理
    disconnected_users = []
    # 遍历用户连接字典，逐个发送消息
    for user_id, connection in userdict.items():
        try:
            # 尝试通过WebSocket连接发送消息
            # send_text方法是FastAPI WebSocket连接对象的方法
            await connection.send_text(message_text)
        except Exception as e:
            # 捕获发送异常，记录日志并标记为断开连接
            print(f"[广播] 向用户 {user_id} 发送消息失败: {e}")
            # 将发送失败的用户ID添加到断开连接列表中
            disconnected_users.append(user_id)
    
    # 清理断开的用户连接
    # 遍历发送失败的用户列表，从用户连接字典中移除
    for user_id in disconnected_users:
        # 使用pop方法安全地移除键值对，避免KeyError异常
        # None参数表示如果键不存在时不抛出异常
        userdict.pop(user_id, None)
    
    # 向所有客服广播消息
    # disconnected_services列表用于收集发送失败的客服ID，后续统一清理
    disconnected_services = []
    # 遍历客服连接字典，逐个发送消息
    for service_id, connection in servicedict.items():
        try:
            # 尝试通过WebSocket连接发送消息
            await connection.send_text(message_text)
        except Exception as e:
            # 捕获发送异常，记录日志并标记为断开连接
            print(f"[广播] 向客服 {service_id} 发送消息失败: {e}")
            # 将发送失败的客服ID添加到断开连接列表中
            disconnected_services.append(service_id)
    
    # 清理断开的客服连接
    # 遍历发送失败的客服列表，从客服连接字典中移除
    for service_id in disconnected_services:
        # 使用pop方法安全地移除键值对，避免KeyError异常
        servicedict.pop(service_id, None)


async def send_to_client(client_id: str, message: dict, is_service: bool = False):
    """
    向指定客户端发送消息
    
    精准发送机制说明：
    1. 根据客户端类型（用户或客服）选择对应连接池
    2. 检查客户端是否在线（连接是否存在）
    3. 发送消息并处理可能的异常
    4. 自动清理已断开的连接
    
    参数:
        client_id: 客户端ID（用户ID或客服ID）
              用于在连接池中定位特定客户端
        message: 要发送的消息（字典格式）
              包含具体的消息内容和元数据
        is_service: True表示发送给客服，False表示发送给用户
              布尔标志用于区分客户端类型
    """
    # 将消息字典序列化为JSON字符串，确保中文字符正确处理
    message_text = json.dumps(message, ensure_ascii=False)
    
    # 根据客户端类型判断发送目标
    if is_service:
        # 发送给客服的处理逻辑
        # 检查客服是否在线（连接是否存在）
        if client_id in servicedict:
            try:
                # 尝试通过WebSocket连接发送消息
                await servicedict[client_id].send_text(message_text)
                # 发送成功，记录日志
                print(f"[客服消息] 已发送给客服 {client_id}")
                # 返回发送成功标志
                return True
            except Exception as e:
                # 发送失败，记录错误日志
                print(f"[客服消息] 发送失败: {e}")
                # 从客服连接字典中移除已断开的连接
                servicedict.pop(client_id, None)
                # 返回发送失败标志
                return False
        else:
            # 客服不在线，记录日志
            print(f"[客服消息] 客服 {client_id} 不在线")
            # 返回发送失败标志
            return False
    else:
        # 发送给用户的处理逻辑
        # 检查用户是否在线（连接是否存在）
        if client_id in userdict:
            try:
                # 尝试通过WebSocket连接发送消息
                await userdict[client_id].send_text(message_text)
                # 发送成功，记录日志
                print(f"[用户消息] 已发送给用户 {client_id}")
                # 返回发送成功标志
                return True
            except Exception as e:
                # 发送失败，记录错误日志
                print(f"[用户消息] 发送失败: {e}")
                # 从用户连接字典中移除已断开的连接
                userdict.pop(client_id, None)
                # 返回发送失败标志
                return False
        else:
            # 用户不在线，记录日志
            print(f"[用户消息] 用户 {client_id} 不在线")
            # 返回发送失败标志
            return False


def get_room_id(user_id: str) -> str:
    """
    生成房间号（userid + serviceid）
    
    房间号设计说明：
    1. 采用"用户ID_客服ID"的格式，确保唯一性
    2. 便于识别聊天会话的参与者
    3. 支持将来扩展为多客服系统
    
    参数:
        user_id: 用户ID
              用于生成特定用户与客服的会话房间号
    
    返回:
        房间号字符串
              格式为"{user_id}_{serviceid}"，如"user123_1001"
    """
    # 使用f-string格式化字符串，将用户ID和客服ID组合成房间号
    # 这种设计确保每个用户与客服的会话都有唯一的标识符
    return f"{user_id}_{serviceid}"


def is_service_client(client_id: str) -> bool:
    """
    判断客户端是否为客服
    
    客户端类型识别机制：
    1. 通过比较客户端ID与预设的客服ID进行判断
    2. 返回布尔值表示客户端类型
    3. 为消息路由和权限控制提供依据
    
    参数:
        client_id: 客户端ID
              待判断的客户端唯一标识符
    
    返回:
        True表示是客服，False表示是用户
              基于ID匹配的布尔判断结果
    """
    # 通过严格相等比较判断客户端ID是否等于预设的客服ID
    # 如果相等则返回True（表示是客服），否则返回False（表示是用户）
    return client_id == serviceid